package com.xafc.demo.security.utils;

import cn.hutool.core.util.StrUtil;
import com.xafc.demo.security.config.JwtSecurityProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.DefaultClock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtTokenUtil {
    private final static Clock clock = DefaultClock.INSTANCE;

    private static JwtSecurityProperties jwtSecurityProperties;

    @Autowired
    public JwtTokenUtil(JwtSecurityProperties jwtSecurityProperties) {
        JwtTokenUtil.jwtSecurityProperties = jwtSecurityProperties;
    }

    /**
     * 生成token信息
     * @param userMap 用户信息
     * @return
     */
    public static String generateToken(Map<String, Object> userMap) {
        final Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userMap.get("userId"));
        claims.put("username", userMap.get("username"));
        return doGenerateToken(claims, userMap.get("username").toString());
    }

    /**
     * 签发令牌
     * @param claims
     * @param subject
     * @return
     */
    private static String doGenerateToken(Map<String, Object> claims, String subject) {
        final Date createdDate = clock.now();
        final Date expirationDate = new Date(createdDate.getTime() + (jwtSecurityProperties.getNormalExpires() * 1000L));
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, jwtSecurityProperties.getSecret())
                // 签发token
                .compact();
    }

    /**
     * 验证token
     * @param token
     * @param currentUser
     * @return
     */
    public static Boolean validateToken(String token, CurrentUser currentUser) {
        final String username = getUsernameFromToken(tokenStartFormat(token));
        return (username.equals(currentUser.getUsername())
                && !isTokenExpired(tokenStartFormat(token))
        );
    }

    /**
     * 获取Claim中对应的信息，claimsResolver策略模式
     * @param token
     * @param claimsResolver
     * @param <T>
     * @return
     */
    public static  <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(tokenStartFormat(token));
        return claimsResolver.apply(claims);
    }

    /**
     * 根据token获取当前用户
     * @param token
     * @return
     */
    public static CurrentUser parseJWTToUserInfo(String token) {
        final Claims claims = getClaimFromToken(tokenStartFormat(token), Function.identity());
        final CurrentUser currentUser = new CurrentUser();
        currentUser.setUserId(Integer.valueOf(claims.get("userId").toString()));
        currentUser.setUsername(claims.get("username").toString());
        return currentUser;
    }

    /**
     * 获取Token载荷
     * @param token 凭据
     * @return 载荷
     */
    private static Claims getAllClaimsFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(jwtSecurityProperties.getSecret())
                .parseClaimsJws(tokenStartFormat(token))
                .getBody();
    }

    /**
     * 验证Token是否过期
     * @param token 凭据
     * @return 是否过期
     */
    public static Boolean isTokenExpired(String token) {
        final Date expiration;
        try {
            expiration = getExpirationDateFromToken(tokenStartFormat(token));
        }catch (Exception e){
            return true;
        }
        return expiration.before(clock.now());
    }

    /**
     * 获取用户名称
     * @param token 凭据
     * @return 用户名
     */
    public static String getUsernameFromToken(String token) {
        return getClaimFromToken(tokenStartFormat(token), Claims::getSubject);
    }

    /**
     * 获取过期时间
     * @param token 凭据
     * @return 时间
     */
    private static Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(tokenStartFormat(token), Claims::getExpiration);
    }

    /**
     * 去掉token 开头的标记
     */
    private static String tokenStartFormat(String token){
        if(StrUtil.startWith(token, jwtSecurityProperties.getHeadToken())){
            return StrUtil.replace(token, jwtSecurityProperties.getHeadToken(), StrUtil.EMPTY);
        }
        return token;
    }

}
